#include "il2cpp-config.h"

#if IL2CPP_TARGET_POSIX

#include "FileHandle.h"

#if IL2CPP_USE_POSIX_FILE_PLATFORM_CONFIG
#include "FilePlatformConfig.h"
#endif


#include "os/ErrorCodes.h"
#include "os/File.h"
#include "os/Mutex.h"
#include "os/Posix/Error.h"
#include "utils/PathUtils.h"

#include <assert.h>
#include <fcntl.h>
#include <stdint.h>
#include <unistd.h>
#include <utime.h>
#include <sys/errno.h>
#include <sys/stat.h>
#include <sys/types.h>

#define INVALID_FILE_HANDLE		(FileHandle*)-1;
#define INVALID_FILE_ATTRIBUTES	(FileAttributes)((uint32_t)-1);
#define TIME_ZERO				116444736000000000ULL

namespace il2cpp
{
namespace os
{

// Head and tail of linked list.
static FileHandle* s_fileHandleHead = NULL;
static FileHandle* s_fileHandleTail = NULL;
static FastMutex s_fileHandleMutex;

static void AddFileHandle (FileHandle *fileHandle)
{
	FastAutoLock autoLock(&s_fileHandleMutex);

	if (s_fileHandleHead == NULL)
	{
		assert(s_fileHandleTail == NULL);

		s_fileHandleHead = fileHandle;
		s_fileHandleTail = fileHandle;
	}
	else
	{
		assert(s_fileHandleTail != NULL);
		assert(s_fileHandleTail->next == NULL);

		s_fileHandleTail->next = fileHandle;
		fileHandle->prev = s_fileHandleTail;
		s_fileHandleTail = fileHandle;
	}
}

static void RemoveFileHandle (FileHandle *fileHandle)
{
	FastAutoLock autoLock(&s_fileHandleMutex);

	if(s_fileHandleHead == fileHandle)
		s_fileHandleHead = fileHandle->next;

	if(s_fileHandleTail == fileHandle)
		s_fileHandleTail = fileHandle->prev;

	if(fileHandle->prev)
		fileHandle->prev->next = fileHandle->next;

	if(fileHandle->next)
		fileHandle->next->prev = fileHandle->prev;
}

static const FileHandle* FindFileHandle (const struct stat& statBuf)
{
	FastAutoLock autoLock(&s_fileHandleMutex);

	const dev_t device = statBuf.st_dev;
	const ino_t inode = statBuf.st_ino;

	for(FileHandle *handle = s_fileHandleHead; handle != NULL; handle = handle->next)
	{
		if (handle->device == device && handle->inode == inode)
			return handle;
	}

	return NULL;
}

// NOTE:
// Checking for file sharing violations only works for the current process.
//
// Mono implements this feature across processes by storing the file handles as
// a look up table in a shared file.

static bool ShareAllowOpen (const struct stat& statBuf, int shareMode, int accessMode)
{
	const FileHandle *fileHandle = FindFileHandle(statBuf);

	if(fileHandle == NULL) // File is not open
		return true;

	if(fileHandle->shareMode == File::kFileShareNone)
		return false;

	if( ( (fileHandle->shareMode == File::kFileShareRead)  && (accessMode != File::kFileAccessRead)  ) ||
		( (fileHandle->shareMode == File::kFileShareWrite) && (accessMode != File::kFileAccessWrite) ) )
	{
		return false;
	}

	if( ( (fileHandle->accessMode & File::kFileAccessRead)  && !(shareMode & File::kFileShareRead)  ) ||
		( (fileHandle->accessMode & File::kFileAccessWrite) && !(shareMode & File::kFileShareWrite) ) )
	{
		return false;
	}

	return true;
}

static File::FileAttributes StatToFileAttribute(const std::string& path, struct stat& pathStat, struct stat* linkStat)
{
	uint32_t fileAttributes = 0;

	if (S_ISSOCK (pathStat.st_mode))
		pathStat.st_mode &= ~S_IFSOCK; // don't consider socket protection

#if defined(__APPLE__) && defined(__MACH__)
	if ((pathStat.st_flags & UF_IMMUTABLE) || (pathStat.st_flags & SF_IMMUTABLE))
		fileAttributes |= File::kFileAttributeReadOnly;
#endif

	const std::string filename (il2cpp::utils::PathUtils::Basename (path));

	if (S_ISDIR (pathStat.st_mode))
	{
		fileAttributes = File::kFileAttributeDirectory;

		if (!(pathStat.st_mode & S_IWUSR) && !(pathStat.st_mode & S_IWGRP) && !(pathStat.st_mode & S_IWOTH))
			fileAttributes |= File::kFileAttributeReadOnly;

		if (filename[0] == '.')
			fileAttributes |= File::kFileAttributeHidden;
	}
	else
	{
		if (!(pathStat.st_mode & S_IWUSR) && !(pathStat.st_mode & S_IWGRP) && !(pathStat.st_mode & S_IWOTH))
		{
			fileAttributes = File::kFileAttributeReadOnly;

			if (filename[0] == '.')
				fileAttributes |= File::kFileAttributeHidden;
		}
		else if (filename[0] == '.')
			fileAttributes = File::kFileAttributeHidden;
		else
			fileAttributes = File::kFileAttributeNormal;
	}

	if (linkStat != NULL && S_ISLNK (linkStat->st_mode))
		fileAttributes |= File::kFileAttributeReparse_point;

	return (File::FileAttributes)fileAttributes;
}

static int GetStatAndLinkStat(const std::string& path, struct stat& pathStat, struct stat& linkStat)
{
	const int statResult = stat(path.c_str(), &pathStat);

	if(statResult == -1 && errno == ENOENT && lstat(path.c_str(), &pathStat) != 0) // Might be a dangling symlink...
		return PathErrnoToErrorCode (path, errno);

	if(lstat(path.c_str(), &linkStat) != 0)
		return PathErrnoToErrorCode (path, errno);

	return kErrorCodeSuccess;
}

static uint64_t TimeToTicks (time_t timeval)
{
	return ((uint64_t)timeval * 10000000) + TIME_ZERO;
}

static time_t TicksToTime(uint64_t ticks)
{
	return (ticks - TIME_ZERO) / 10000000;
}

static bool InternalCopyFile(int srcFd, int destFd, const struct stat& srcStat, int *error)
{
	const blksize_t preferedBlockSize = srcStat.st_blksize;
	const blksize_t bufferSize = preferedBlockSize < 8192 ? 8192 : (preferedBlockSize > 65536 ? 65536 : preferedBlockSize);

	char *buffer = new char[bufferSize];

	ssize_t readBytes;

	while((readBytes = read (srcFd, buffer, bufferSize)) > 0)
	{
		char* writeBuffer = buffer;
		ssize_t writeBytes = readBytes;

		while(writeBytes > 0)
		{
			const ssize_t writtenBytes = write(destFd, writeBuffer, writeBytes);

			if(writtenBytes < 0)
			{
				if(errno == EINTR)
					continue;

				delete[] buffer;

				*error = FileErrnoToErrorCode (errno);
				return false;
			}

			writeBytes -= writtenBytes;
			writeBuffer += writtenBytes;
		}
	}

	delete[] buffer;

	if(readBytes < 0)
	{
		*error = FileErrnoToErrorCode (errno);
		return false;
	}

	assert(readBytes == 0);

	return true;
}

bool File::Isatty(FileHandle* fileHandle)
{
	return isatty(fileHandle->fd) == 1;
}

FileHandle* File::GetStdError ()
{
	static FileHandle* s_handle = NULL;
	if (s_handle)
		return s_handle;

	s_handle = new FileHandle();
	s_handle->fd = 2;
	s_handle->type = File::kFileTypeChar;
	s_handle->options = 0;
	s_handle->accessMode = kFileAccessReadWrite;
	s_handle->shareMode = -1; // Only used for files

	return s_handle;
}

FileHandle* File::GetStdInput ()
{
	static FileHandle* s_handle = NULL;
	if (s_handle)
		return s_handle;

	s_handle = new FileHandle();
	s_handle->fd = 0;
	s_handle->type = File::kFileTypeChar;
	s_handle->options = 0;
	s_handle->accessMode = kFileAccessRead;
	s_handle->shareMode = -1; // Only used for files

	return s_handle;
}

FileHandle* File::GetStdOutput ()
{
	static FileHandle* s_handle = NULL;
	if (s_handle)
		return s_handle;

	s_handle = new FileHandle();
	s_handle->fd = 1;
	s_handle->type = File::kFileTypeChar;
	s_handle->options = 0;
	s_handle->accessMode = kFileAccessReadWrite;
	s_handle->shareMode = -1; // Only used for files

	return s_handle;
}

bool File::CreatePipe (FileHandle** read_handle, FileHandle** write_handle)
{
	int fds[2];

	const int ret = pipe (fds);

	if(ret == -1)
		return false;

	FileHandle *input = new FileHandle();
	input->fd = fds[0];
	input->type = File::kFileTypePipe;
	input->options = 0;
	input->accessMode = kFileAccessRead;
	input->shareMode = -1; // Only used for files

	FileHandle *output = new FileHandle();
	output->fd = fds[1];
	output->type = File::kFileTypePipe;
	output->options = 0;
	output->accessMode = kFileAccessReadWrite;
	output->shareMode = -1; // Only used for files

	*read_handle = input;
	*write_handle = output;

	return true;
}

File::FileAttributes File::GetFileAttributes (const std::string& path, int *error)
{
	struct stat pathStat, linkStat;

	*error = GetStatAndLinkStat(path, pathStat, linkStat);

	if (*error != kErrorCodeSuccess)
		return INVALID_FILE_ATTRIBUTES;

	return StatToFileAttribute(path, pathStat, &linkStat);
}

bool File::SetFileAttributes (const std::string& path, FileAttributes attributes, int* error)
{
	struct stat pathStat;

	int ret = stat(path.c_str(), &pathStat);

	if(ret != 0)
	{
		*error = PathErrnoToErrorCode (path, errno);
		return false;
	}

	if(attributes & kFileAttributeReadOnly)
		ret = chmod (path.c_str(), pathStat.st_mode & ~(S_IWUSR | S_IWOTH | S_IWGRP));
	else
		ret = chmod (path.c_str(), pathStat.st_mode | S_IWUSR);

	if(ret != 0)
	{
		*error = PathErrnoToErrorCode (path, errno);
		return false;
	}

	// Mono ignores all other attributes

	if (attributes & kFileAttributeInternalMonoExecutable)
	{
		mode_t exec_mask = 0;

		if ((pathStat.st_mode & S_IRUSR) != 0)
			exec_mask |= S_IXUSR;

		if ((pathStat.st_mode & S_IRGRP) != 0)
			exec_mask |= S_IXGRP;

		if ((pathStat.st_mode & S_IROTH) != 0)
			exec_mask |= S_IXOTH;

		ret = chmod (path.c_str(), pathStat.st_mode | exec_mask);

		if(ret != 0)
		{
			*error = PathErrnoToErrorCode (path, errno);
			return false;
		}
	}

	return true;
}

bool File::GetFileStat (const std::string& path, il2cpp::os::FileStat * stat, int* error)
{
	struct stat pathStat, linkStat;

	*error = GetStatAndLinkStat(path, pathStat, linkStat);

	if (*error != kErrorCodeSuccess)
		return false;

	const std::string filename(	il2cpp::utils::PathUtils::Basename (path));
	const time_t creationTime = pathStat.st_mtime < pathStat.st_ctime ? pathStat.st_mtime : pathStat.st_ctime;

	stat->name = filename;

	stat->attributes = StatToFileAttribute(path, pathStat, &linkStat);

	stat->length = (stat->attributes & kFileAttributeDirectory) > 0 ? 0 : pathStat.st_size;

	stat->creation_time = TimeToTicks(creationTime);
	stat->last_access_time = TimeToTicks(pathStat.st_atime);
	stat->last_write_time = TimeToTicks(pathStat.st_mtime);

	return true;
}

File::FileType File::GetFileType (FileHandle* handle)
{
	return ((FileHandle*)handle)->type;
}

bool File::DeleteFile (const std::string& path, int *error)
{
	const FileAttributes attributes = GetFileAttributes(path, error);

	if(*error != kErrorCodeSuccess)
	{
		return false;
	}

	if(attributes & kFileAttributeReadOnly)
	{
		*error = kErrorCodeAccessDenied;
		return false;
	}

	const int ret = unlink(path.c_str());

	if(ret == -1)
	{
		*error = PathErrnoToErrorCode (path, errno);
		return false;
	}

	*error = kErrorCodeSuccess;
	return true;
}

bool File::CopyFile (const std::string& src, const std::string& dest, bool overwrite, int* error)
{
	const int srcFd = open(src.c_str(), O_RDONLY, 0);

	if(srcFd < 0)
	{
		*error = PathErrnoToErrorCode(src, errno);
		return false;
	}

	struct stat srcStat;

	if(fstat(srcFd, &srcStat) < 0)
	{
		*error = FileErrnoToErrorCode(errno);
		close(srcFd);
		return false;
	}

	int destFd;

	if(!overwrite)
	{
		destFd = open(dest.c_str(), O_WRONLY | O_CREAT | O_EXCL, srcStat.st_mode);
	}
	else
	{
		destFd = open (dest.c_str(), O_WRONLY | O_TRUNC, srcStat.st_mode);

		if(destFd < 0)
			destFd = open (dest.c_str(), O_WRONLY | O_CREAT | O_TRUNC, srcStat.st_mode);
		else
			*error = kErrorCodeAlreadyExists; // Apparently this error is set if we overwrite the dest file
	}

	if(destFd < 0)
	{
		*error = FileErrnoToErrorCode (errno);
		close (srcFd);
		return false;
	}

	const bool ret = InternalCopyFile(srcFd, destFd, srcStat, error);

	close (srcFd);
	close (destFd);

	return ret;
}

bool File::MoveFile (const std::string& src, const std::string& dest, int* error)
{
	struct stat srcStat, destStat;

	if(stat(src.c_str(), &srcStat) < 0)
	{
		*error = PathErrnoToErrorCode(src.c_str(), errno);
		return false;
	}

	// In C# land we check for the existence of src, but not for dest.
	// We check it here and return the failure if dest exists and is not
	// the same file as src.
	if(stat(dest.c_str(), &destStat) == 0) // dest exists
	{
		if (destStat.st_dev != srcStat.st_dev || destStat.st_ino != srcStat.st_ino)
		{
			*error = kErrorCodeAlreadyExists;
			return false;
		}
	}

	if(!ShareAllowOpen(srcStat, kFileShareNone, kFileAccessWrite))
	{
		*error = kErrorCodeSharingViolation;
		return false;
	}

	const int ret = rename(src.c_str(), dest.c_str());

	if(ret == -1)
	{
		if(errno == EEXIST)
		{
			*error = kErrorCodeAlreadyExists;
			return false;
		}
		else if(errno == EXDEV)
		{
			if(S_ISDIR(srcStat.st_mode))
			{
				*error = kErrorCodeNotSameDevice;
				return false;
			}

			if(!CopyFile(src, dest, true, error))
			{
				// CopyFile sets the error
				return false;
			}

			return DeleteFile(src, error); // DeleteFile sets the error
		}
		else
		{
			*error = PathErrnoToErrorCode(src.c_str(), errno);
			return false;
		}
	}

	*error = kErrorCodeSuccess;
	return true;
}

bool File::ReplaceFile (const std::string& sourceFileName, const std::string& destinationFileName, const std::string& destinationBackupFileName, bool ignoreMetadataErrors, int* error)
{
	const bool backupFile = !destinationBackupFileName.empty();

	// Open the backup file for read so we can restore the file if an error occurs.
	const int backupFd = backupFile ? open (destinationBackupFileName.c_str(), O_RDONLY, 0) : -1;

	// dest -> backup
	if(backupFile)
	{
		const int retDest = rename(destinationFileName.c_str(), destinationBackupFileName.c_str());

		if(retDest == -1)
		{
			if(backupFd != -1)
				close (backupFd);

			*error = PathErrnoToErrorCode(destinationFileName.c_str(), errno);
			return false;
		}
	}

	// source -> dest
	const int restSource = rename(sourceFileName.c_str(), destinationFileName.c_str());

	if(restSource == -1)
	{
		// backup -> dest
		if(backupFile)
			rename (destinationBackupFileName.c_str(), destinationFileName.c_str());

		// Copy backup data -> dest
		struct stat backupStat;
		if(backupFd != -1 && fstat(backupFd, &backupStat) == 0)
		{
			const int destFd = open (destinationBackupFileName.c_str(), O_WRONLY | O_CREAT | O_TRUNC, backupStat.st_mode);

			if(destFd != -1)
			{
				int unusedCopyFileError;
				InternalCopyFile(backupFd, destFd, backupStat, &unusedCopyFileError);
				close (destFd);
			}
		}

		if(backupFd != -1)
			close (backupFd);

		*error = PathErrnoToErrorCode(sourceFileName.c_str(), errno);
		return false;
	}

	if(backupFd != -1)
		close (backupFd);

	*error = kErrorCodeSuccess;

	return true;
}

static int ConvertFlags(int fileaccess, int createmode)
{
	int flags;

	switch(fileaccess)
	{
	case File::kFileAccessRead:
		flags = O_RDONLY;
		break;
	case File::kFileAccessWrite:
		flags = O_WRONLY;
		break;
	case File::kFileAccessReadWrite:
		flags = O_RDWR;
		break;
	default:
		flags = 0;
		break;
	}

	switch(createmode)
	{
	case File::kFileModeCreateNew:
		flags |= O_CREAT|O_EXCL;
		break;
	case File::kFileModeCreate:
		flags |= O_CREAT|O_TRUNC;
		break;
	case File::kFileModeOpen:
		break;
	case File::kFileModeOpenOrCreate:
	case File::kFileModeAppend:
		flags |= O_CREAT;
		break;
	case File::kFileModeTruncate:
		flags |= O_TRUNC;
		break;
	default:
		flags = 0;
		break;
	}

	return flags;
}

#ifndef S_ISFIFO
#define S_ISFIFO(m) ((m & S_IFIFO) != 0)
#endif

FileHandle* File::Open (const std::string& path, int mode, int accessMode, int shareMode, int options, int *error)
{
	const int flags = ConvertFlags(accessMode, mode);

	/* we don't use sharemode, because that relates to sharing of
	 * the file when the file is open and is already handled by
	 * other code, perms instead are the on-disk permissions and
	 * this is a sane default.
	 */
	const mode_t perms = options & kFileOptionsTemporary ? 0600 : 0666;

	int fd = open(path.c_str(), flags, perms);

	/* If we were trying to open a directory with write permissions
	 * (e.g. O_WRONLY or O_RDWR), this call will fail with
	 * EISDIR. However, this is a bit bogus because calls to
	 * manipulate the directory (e.g. SetFileTime) will still work on
	 * the directory because they use other API calls
	 * (e.g. utime()). Hence, if we failed with the EISDIR error, try
	 * to open the directory again without write permission.
	 */

	// Try again but don't try to make it writable
	if (fd == -1)
	{
		if(errno == EISDIR)
		{
			fd = open (path.c_str(), flags & ~(O_RDWR|O_WRONLY), perms);

			if(fd == -1)
			{
				*error = PathErrnoToErrorCode (path, errno);
				return INVALID_FILE_HANDLE;
			}
		}
		else
		{
			*error = PathErrnoToErrorCode (path, errno);
			return INVALID_FILE_HANDLE;
		}
	}

	struct stat statbuf;
	const int ret = fstat(fd, &statbuf);

	if(ret == -1)
	{
		*error = FileErrnoToErrorCode (errno);
		close(fd);
		return INVALID_FILE_HANDLE;
	}

	if(!ShareAllowOpen(statbuf, shareMode, accessMode))
	{
		*error = kErrorCodeSharingViolation;
		close(fd);
		return INVALID_FILE_HANDLE;
	}

	FileHandle* fileHandle = new FileHandle();
	fileHandle->fd = fd;
	fileHandle->path = path;
	fileHandle->options = options;
	fileHandle->accessMode = accessMode;
	fileHandle->shareMode = shareMode;

	fileHandle->device = statbuf.st_dev;
	fileHandle->inode = statbuf.st_ino;

	// Add to linked list
	AddFileHandle(fileHandle);

#ifdef HAVE_POSIX_FADVISE
	if (options & kFileOptionsSequentialScan)
		posix_fadvise (fd, 0, 0, POSIX_FADV_SEQUENTIAL);
	if (options & kFileOptionsRandomAccess)
		posix_fadvise (fd, 0, 0, POSIX_FADV_RANDOM);
#endif

	if (S_ISFIFO (statbuf.st_mode))
		fileHandle->type = File::kFileTypePipe;
	else if (S_ISCHR (statbuf.st_mode))
		fileHandle->type = File::kFileTypeChar;
	else
		fileHandle->type = File::kFileTypeDisk;

	*error = kErrorCodeSuccess;

	return fileHandle;
}

bool File::Close (FileHandle* handle, int *error)
{
	if(handle->type == kFileTypeDisk && handle->options & kFileOptionsDeleteOnClose)
		unlink (handle->path.c_str());

	close (handle->fd);

	// Remove from linked list
	RemoveFileHandle(handle);

	delete handle;

	*error = kErrorCodeSuccess;

	return true;
}

bool File::SetFileTime (FileHandle* handle, int64_t creation_time, int64_t last_access_time, int64_t last_write_time, int* error)
{
	if((handle->accessMode & kFileAccessWrite) == 0)
	{
		*error = kErrorCodeAccessDenied;
		return false;
	}

	struct stat statbuf;
	const int ret = fstat (handle->fd, &statbuf);

	if(ret == -1)
	{
		*error = kErrorCodeInvalidParameter;
		return false;
	}

	struct utimbuf utbuf;

	// Setting creation time is not implemented in Mono and not supported by utime.

	if(last_access_time >= 0)
	{
		if (last_access_time < TIME_ZERO)
		{
			*error = kErrorCodeInvalidParameter;
			return false;
		}

		utbuf.actime = TicksToTime(last_access_time);
	}
	else
	{
		utbuf.actime = statbuf.st_atime;
	}

	if(last_write_time >= 0)
	{
		if (last_write_time < TIME_ZERO)
		{
			*error = kErrorCodeInvalidParameter;
			return false;
		}

		utbuf.modtime = TicksToTime(last_write_time);
	}
	else
	{
		utbuf.modtime = statbuf.st_mtime;
	}

	const int utimeRet = utime (handle->path.c_str(), &utbuf);

	if(utimeRet == -1)
	{
		*error = kErrorCodeInvalidParameter;
		return false;
	}

	*error = kErrorCodeSuccess;

	return true;
}

int64_t File::GetLength (FileHandle* handle, int *error)
{
	if (handle->type != kFileTypeDisk)
	{
		*error = kErrorCodeInvalidHandle;
		return false;
	}

	struct stat statbuf;

	const int ret = fstat(handle->fd, &statbuf);

	if (ret == -1)
	{
		*error = FileErrnoToErrorCode (errno);
		return -1;
	}

	*error = kErrorCodeSuccess;

	return statbuf.st_size;
}

bool File::SetLength (FileHandle* handle, int64_t length, int *error)
{
	if (handle->type != kFileTypeDisk)
	{
		*error = kErrorCodeInvalidHandle;
		return false;
	}

	// Save current position
	const off_t currentPosition = lseek(handle->fd, 0, SEEK_CUR);

	if(currentPosition == -1)
	{
		*error = FileErrnoToErrorCode (errno);
		return false;
	}

	const off_t setLength = lseek(handle->fd, length, SEEK_SET);

	if(setLength == -1)
	{
		*error = FileErrnoToErrorCode (errno);
		return false;
	}

	int ret = 0;

	do
	{
		ret = ftruncate(handle->fd, length);
	}
	while(ret == -1 && errno == EINTR);

	if (ret == -1)
	{
		*error = FileErrnoToErrorCode (errno);
		return false;
	}

	const off_t oldPosition = lseek(handle->fd, currentPosition, SEEK_SET);

	if(oldPosition == -1)
	{
		*error = FileErrnoToErrorCode (errno);
		return false;
	}

	*error = kErrorCodeSuccess;

	return true;
}

int64_t File::Seek (FileHandle* handle, int64_t offset, int origin, int *error)
{
	if (handle->type != kFileTypeDisk)
	{
		*error = kErrorCodeInvalidHandle;
		return false;
	}

	int whence;

	switch(origin)
	{
		case kFileSeekOriginBegin:
			whence = SEEK_SET;
			break;
		case kFileSeekOriginCurrent:
			whence = SEEK_CUR;
			break;
		case kFileSeekOriginEnd:
			whence = SEEK_END;
			break;
		default:
		{
			*error = kErrorCodeInvalidParameter;
			return -1;
		}
	};

	const off_t position = lseek(handle->fd, offset, whence);

	if(position == -1)
	{
		*error = FileErrnoToErrorCode (errno);
		return -1;
	}

	*error = kErrorCodeSuccess;

	return position;
}

int File::Read (FileHandle* handle, char *dest, int count, int *error)
{
	if((handle->accessMode & kFileAccessRead) == 0)
	{
		*error = kErrorCodeAccessDenied;
		return 0;
	}

	int ret;

	do
	{
		ret = (int)read (handle->fd, dest, count);
	}
	while (ret == -1 && errno == EINTR);

	if(ret == -1)
	{
		*error = FileErrnoToErrorCode (errno);
		return 0;
	}

	return ret;
}

int32_t File::Write (FileHandle* handle, const char* buffer, int count, int *error)
{
	if((handle->accessMode & kFileAccessWrite) == 0)
	{
		*error = kErrorCodeAccessDenied;
		return 0;
	}

	int ret;

	do
	{
		ret = (int32_t)write (handle->fd, buffer, count);
	}
	while (ret == -1 && errno == EINTR);

	if (ret == -1)
	{
		*error = FileErrnoToErrorCode (errno);
		return 0;
	}

	return ret;
}

bool File::Flush (FileHandle* handle, int* error)
{
	if (handle->type != kFileTypeDisk)
	{
		*error = kErrorCodeInvalidHandle;
		return false;
	}

	const int ret = fsync(handle->fd);

	if (ret == -1)
	{
		*error = FileErrnoToErrorCode (errno);
		return false;
	}

	*error = kErrorCodeSuccess;
	return true;
}

void File::Lock (FileHandle* handle, int64_t position, int64_t length, int* error)
{
	struct flock lock_data;
	int ret;

	lock_data.l_type = F_WRLCK;
	lock_data.l_whence = SEEK_SET;
	lock_data.l_start = position;
	lock_data.l_len = length;

	do
	{
		ret = fcntl (handle->fd, F_SETLK, &lock_data);
	}
	while(ret == -1 && errno == EINTR);

	if (ret == -1)
	{
		/*
		 * if locks are not available (NFS for example),
		 * ignore the error
		 */
		if (errno == ENOLCK
#ifdef EOPNOTSUPP
			|| errno == EOPNOTSUPP
#endif
#ifdef ENOTSUP
			|| errno == ENOTSUP
#endif
		   )
		{
			*error = kErrorCodeSuccess;
			return;
		}

		*error = FileErrnoToErrorCode (errno);
		return;
	}

	*error = kErrorCodeSuccess;
	return;
}

void File::Unlock (FileHandle* handle, int64_t position, int64_t length, int* error)
{
	struct flock lock_data;
	int ret;

	lock_data.l_type = F_UNLCK;
	lock_data.l_whence = SEEK_SET;
	lock_data.l_start = position;
	lock_data.l_len = length;

	do
	{
		ret = fcntl (handle->fd, F_SETLK, &lock_data);
	}
	while(ret == -1 && errno == EINTR);

	if (ret == -1)
	{
		/*
		 * if locks are not available (NFS for example),
		 * ignore the error
		 */
		if (errno == ENOLCK
#ifdef EOPNOTSUPP
			|| errno == EOPNOTSUPP
#endif
#ifdef ENOTSUP
			|| errno == ENOTSUP
#endif
		   )
		{
			*error = kErrorCodeSuccess;
			return;
		}

		*error = FileErrnoToErrorCode (errno);
		return;
	}

	*error = kErrorCodeSuccess;
	return;
}

}
}

#endif
